home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / softwareproperties / SoftwareProperties.py < prev   
Text File  |  2009-09-07  |  26KB  |  678 lines

  1. #  software-properties backend
  2. #
  3. #  Copyright (c) 2004-2007 Canonical Ltd.
  4. #                2004-2005 Michiel Sikkes
  5. #
  6. #  Author: Michiel Sikkes <michiel@eyesopened.nl>
  7. #          Michael Vogt <mvo@debian.org>
  8. #          Sebastian Heinlein <glatzor@ubuntu.com>
  9. #
  10. #  This program is free software; you can redistribute it and/or
  11. #  modify it under the terms of the GNU General Public License as
  12. #  published by the Free Software Foundation; either version 2 of the
  13. #  License, or (at your option) any later version.
  14. #
  15. #  This program is distributed in the hope that it will be useful,
  16. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  18. #  GNU General Public License for more details.
  19. #
  20. #  You should have received a copy of the GNU General Public License
  21. #  along with this program; if not, write to the Free Software
  22. #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  23. #  USA
  24.  
  25. import apt_pkg
  26. from hashlib import md5
  27. import re
  28. import os
  29. import glob
  30. import sys
  31. import shutil
  32. import subprocess
  33. import threading
  34. import atexit
  35. import tempfile
  36. import string
  37. import stat
  38.  
  39. from tempfile import NamedTemporaryFile
  40. from xml.sax.saxutils import escape
  41. from ConfigParser import ConfigParser
  42. from gettext import gettext as _
  43. from urlparse import urlparse
  44.  
  45. from AptAuth import AptAuth
  46. import softwareproperties
  47. import aptsources
  48. import aptsources.distro
  49. from aptsources.sourceslist import SourcesList, SourceEntry
  50. from ppa import AddPPASigningKeyThread, expand_ppa_line
  51.  
  52.  
  53. class SoftwareProperties(object):
  54.  
  55.   # known (whitelisted) channels
  56.   CHANNEL_PATH="/usr/share/app-install/channels/"
  57.  
  58.   # release upgrades policy
  59.   RELEASE_UPGRADES_CONF = "/etc/update-manager/release-upgrades"
  60.   #RELEASE_UPGRADES_CONF = "/tmp/release-upgrades"
  61.   (
  62.     RELEASE_UPGRADES_NEVER,
  63.     RELEASE_UPGRADES_NORMAL,
  64.     RELEASE_UPGRADES_LTS
  65.   ) = range(3)
  66.   release_upgrades_policy_map = {
  67.     RELEASE_UPGRADES_NEVER  : 'never',
  68.     RELEASE_UPGRADES_NORMAL : 'normal',
  69.     RELEASE_UPGRADES_LTS    : 'lts',
  70.   }
  71.   
  72.   def __init__(self, datadir=None, options=None):
  73.     """ Provides the core functionality to configure the used software 
  74.         repositories, the corresponding authentication keys and 
  75.         update automation """
  76.     self.popconfile = "/etc/popularity-contest.conf"
  77.  
  78.     # FIXME: some saner way is needed here
  79.     if datadir == None:
  80.       datadir = "/usr/share/software-properties/"
  81.     self.options = options
  82.     self.datadir = datadir
  83.  
  84.     self.sourceslist = SourcesList()
  85.     self.distro = aptsources.distro.get_distro()
  86.     
  87.     self.seen_server = []
  88.     self.modified_sourceslist = False
  89.  
  90.     self.reload_sourceslist()
  91.     self.backup_sourceslist()
  92.  
  93.     self.backup_apt_conf()
  94.  
  95.     # FIXME: we need to store this value in a config option
  96.     #self.custom_mirrors = ["http://adasdwww.de/ubuntu"]
  97.     self.custom_mirrors= []
  98.  
  99.     # apt-key stuff
  100.     self.apt_key = AptAuth()
  101.  
  102.     atexit.register(self.wait_for_threads)
  103.  
  104.   def wait_for_threads(self):
  105.     " wait for all running threads (PPA key fetchers) to exit "
  106.     for t in threading.enumerate():
  107.       if t.ident != threading.current_thread().ident:
  108.         t.join()
  109.  
  110.   def backup_apt_conf(self):
  111.     """Backup all apt configuration options"""
  112.     self.apt_conf_backup = {}
  113.     for option in softwareproperties.CONF_MAP.keys():
  114.         value = apt_pkg.Config.FindI(softwareproperties.CONF_MAP[option])
  115.         self.apt_conf_backup[option] = value
  116.  
  117.   def restore_apt_conf(self):
  118.     """Restore the stored apt configuration"""
  119.     for option in self.apt_conf_backup.keys():
  120.         apt_pkg.Config.Set(softwareproperties.CONF_MAP[option],
  121.                            str(self.apt_conf_backup[option]))
  122.     self.write_config()
  123.  
  124.   def get_update_automation_level(self):
  125.     """ Parse the apt cron configuration. Try to fit a predefined use case 
  126.         and return it. Special case: if the user made a custom 
  127.         configurtation, that we cannot represent it will return None """
  128.     if apt_pkg.Config.FindI(softwareproperties.CONF_MAP["autoupdate"]) > 0:
  129.         # Autodownload
  130.         if apt_pkg.Config.FindI(softwareproperties.CONF_MAP["unattended"]) == 1\
  131.            and apt_pkg.Config.FindI(softwareproperties.CONF_MAP["autodownload"]) == 1 :
  132.             return softwareproperties.UPDATE_INST_SEC
  133.         elif apt_pkg.Config.FindI(softwareproperties.CONF_MAP["autodownload"]) == 1 and  \
  134.              apt_pkg.Config.FindI(softwareproperties.CONF_MAP["unattended"]) == 0:
  135.             return softwareproperties.UPDATE_DOWNLOAD
  136.         elif apt_pkg.Config.FindI(softwareproperties.CONF_MAP["unattended"]) == 0 and \
  137.              apt_pkg.Config.FindI(softwareproperties.CONF_MAP["autodownload"]) == 0:
  138.             return softwareproperties.UPDATE_NOTIFY
  139.         else:
  140.             return None
  141.     elif apt_pkg.Config.FindI(softwareproperties.CONF_MAP["unattended"]) == 0 and \
  142.          apt_pkg.Config.FindI(softwareproperties.CONF_MAP["autodownload"]) == 0:
  143.         return softwareproperties.UPDATE_MANUAL
  144.     else:
  145.         return None
  146.  
  147.   def set_update_automation_level(self, state):
  148.     """ Set the apt periodic configurtation to the selected 
  149.         update automation level. To synchronize the cache update and the 
  150.         actual upgrading function, the upgrade function, e.g. unattended, 
  151.         will run every day, if enabled. """
  152.     if state == softwareproperties.UPDATE_INST_SEC:
  153.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["unattended"], str(1))
  154.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["autodownload"], str(1))
  155.     elif state == softwareproperties.UPDATE_DOWNLOAD:
  156.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["autodownload"], str(1))
  157.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["unattended"], str(0))
  158.     elif state == softwareproperties.UPDATE_NOTIFY:
  159.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["autodownload"], str(0))
  160.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["unattended"], str(0))
  161.     else:
  162.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["autoupdate"], str(0))
  163.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["unattended"], str(0))
  164.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["autodownload"], str(0))
  165.     self.set_modified_config()
  166.  
  167.   def set_update_interval(self, days):
  168.       """Set the interval in which we check for available updates"""
  169.       # Only write the key if it has changed
  170.       if not days == apt_pkg.Config.FindI(softwareproperties.CONF_MAP["autoupdate"]):
  171.           apt_pkg.Config.Set(softwareproperties.CONF_MAP["autoupdate"], str(days))
  172.           self.set_modified_config()
  173.  
  174.   def get_update_interval(self):
  175.     """ Returns the interval of the apt periodic cron job """
  176.     return apt_pkg.Config.FindI(softwareproperties.CONF_MAP["autoupdate"])
  177.  
  178.   def get_release_upgrades_policy(self):
  179.     """
  180.     return the release upgrade policy:
  181.      RELASE_UPGRADE_NEVER
  182.      RELASE_UPGRADE_NORMAL
  183.      RELASE_UPGRADE_LTS
  184.     """
  185.     # default (if no option is set) is NORMAL
  186.     if not os.path.exists(self.RELEASE_UPGRADES_CONF):
  187.       return self.RELEASE_UPGRADES_NORMAL
  188.     parser = ConfigParser()
  189.     parser.read(self.RELEASE_UPGRADES_CONF)
  190.     if parser.has_option("DEFAULT","Prompt"):
  191.       type = parser.get("DEFAULT","Prompt").lower()
  192.       for k, v in self.release_upgrades_policy_map.iteritems():
  193.         if v == type:
  194.           return k
  195.     return self.RELEASE_UPGRADES_NORMAL
  196.  
  197.   def set_release_upgrades_policy(self, i):
  198.     """
  199.     set the release upgrade policy:
  200.      RELASE_UPGRADE_NEVER
  201.      RELASE_UPGRADE_NORMAL
  202.      RELASE_UPGRADE_LTS
  203.      """
  204.     # we are note using ConfigParser.write() as it removes comments
  205.     if not os.path.exists(self.RELEASE_UPGRADES_CONF):
  206.       f = open(self.RELEASE_UPGRADES_CONF,"w")
  207.       f.write("[DEFAULT]\nprompt=%s\n"% self.release_upgrades_policy_map[i])
  208.       return True
  209.     f = open(self.RELEASE_UPGRADES_CONF,"r")
  210.     out = NamedTemporaryFile()
  211.     for line in map(string.strip, f.readlines()):
  212.       if line.lower().startswith("prompt"):
  213.         out.write("prompt=%s\n" % self.release_upgrades_policy_map[i])
  214.       else:
  215.         out.write(line+"\n")
  216.     out.flush()
  217.     shutil.copymode(self.RELEASE_UPGRADES_CONF, out.name)
  218.     shutil.copy(out.name, self.RELEASE_UPGRADES_CONF)
  219.     return True
  220.  
  221.   def get_popcon_participation(self):
  222.     """ Will return True if the user wants to participate in the popularity 
  223.         contest. Otherwise it will return False. Special case: if no 
  224.         popcon is installed it will return False """
  225.     if os.path.exists(self.popconfile):
  226.         lines = open(self.popconfile).read().split("\n")
  227.         active = False
  228.         for line in lines:
  229.             try:
  230.                 (key,value) = line.split("=")
  231.                 if key == "PARTICIPATE" and value.strip('"').lower() == "yes":
  232.                     active = True
  233.             except ValueError:
  234.                 continue
  235.         return active
  236.     else:
  237.         return False
  238.  
  239.   def set_popcon_pariticipation(self, is_helpful):
  240.     """ Enable or disable the participation in the popularity contest """
  241.     if is_helpful == True:
  242.         value = "yes"
  243.     else:
  244.         value = "no"
  245.     if os.path.exists(self.popconfile):
  246.         # read the current config and replace the corresponding settings
  247.         # FIXME: should we check the other values, too?
  248.         lines = map(lambda line: re.sub(r'^(PARTICIPATE=)(".+?")',
  249.                                         '\\1"%s"' % value,
  250.                                         line),
  251.                     open(self.popconfile, "r").readlines())
  252.     else:
  253.         # create a new popcon config file
  254.         m = md5()
  255.         m.update(open("/dev/urandom", "r").read(1024))
  256.         id = m.hexdigest()
  257.         lines = []
  258.         lines.append("MY_HOSTID=\"%s\"\n" % id)
  259.         lines.append("PARTICIPATE=\"%s\"\n" % str(value))
  260.         lines.append("USE_HTTP=\"yes\"\n")
  261.     open(self.popconfile, "w").writelines(lines)
  262.  
  263.   def get_source_code_state(self):
  264.     """Return True if all distro componets are also available as 
  265.        source code. Otherwise return Flase. Special case: If the
  266.        configuration cannot be represented return None"""
  267.   
  268.     if len(self.distro.source_code_sources) < 1:
  269.         # we don't have any source code sources, so
  270.         # uncheck the button
  271.         self.distro.get_source_code = False
  272.         return False
  273.     else:
  274.         # there are source code sources, so we check the button
  275.         self.distro.get_source_code = True
  276.         # check if there is a corresponding source code source for
  277.         # every binary source. if not set the checkbutton to inconsistent
  278.         templates = {}
  279.         sources = []
  280.         sources.extend(self.distro.main_sources)
  281.         sources.extend(self.distro.child_sources)
  282.         for source in sources:
  283.             if templates.has_key(source.template):
  284.                 for comp in source.comps:
  285.                     templates[source.template].add(comp)
  286.             else:
  287.                 templates[source.template] = set(source.comps)
  288.         # add fake http sources for the cdrom, since the sources
  289.         # for the cdrom are only available in the internet
  290.         if len(self.distro.cdrom_sources) > 0:
  291.             templates[self.distro.source_template] = self.distro.cdrom_comps
  292.         for source in self.distro.source_code_sources:
  293.             if not templates.has_key(source.template) or \
  294.                (templates.has_key(source.template) and not \
  295.                 (len(set(templates[source.template]) ^ set(source.comps)) == 0\
  296.                  or (len(set(source.comps) ^ self.distro.enabled_comps) == 0))):
  297.                 self.distro.get_source_code = False
  298.                 return None
  299.                 break
  300.     return True
  301.  
  302.   def print_source_entry(self, source):
  303.     """Print the data of a source entry to the command line"""
  304.     for (label, value) in [("URI:", source.uri),
  305.                            ("Comps:", source.comps),
  306.                            ("Enabled:", not source.disabled),
  307.                            ("Valid:", not source.invalid)]:
  308.         print " %s %s" % (label, value)
  309.     if source.template:
  310.         for (label, value) in [("MatchURI:", source.template.match_uri),
  311.                                ("BaseURI:", source.template.base_uri)]:
  312.             print " %s %s" % (label, value)
  313.     print "\n"
  314.  
  315.   def massive_debug_output(self):
  316.     """Print the complete sources.list""" 
  317.     print "START SOURCES.LIST:"
  318.     for source in self.sourceslist:
  319.         print source.str()
  320.     print "END SOURCES.LIST\n"
  321.  
  322.   def enable_component(self, comp):
  323.     """Enable a component of the distro"""
  324.     self.distro.enable_component(comp) 
  325.     self.set_modified_sourceslist()
  326.  
  327.   def disable_component(self, comp):
  328.     """Disable a component of the distro"""
  329.     self.distro.disable_component(comp) 
  330.     self.set_modified_sourceslist()
  331.  
  332.   def disable_child_source(self, template):
  333.     """Enable a child repo of the distribution main repository"""
  334.     for source in self.distro.child_sources:
  335.         if source.template == template:
  336.             self.sourceslist.remove(source)
  337.     for source in self.distro.source_code_sources:
  338.         if source.template == template:
  339.             self.sourceslist.remove(source)
  340.     self.set_modified_sourceslist()
  341.  
  342.   def enable_child_source(self, template):
  343.     """Enable a child repo of the distribution main repository"""
  344.     # Use the currently selected mirror only if the child source
  345.     # did not override the server
  346.     if template.base_uri == None:
  347.         child_uri = self.distro.default_server
  348.     else:
  349.         child_uri = template.base_uri
  350.     self.distro.add_source(uri=child_uri, dist=template.name)
  351.     self.set_modified_sourceslist()
  352.  
  353.   def disable_source_code_sources(self):
  354.     """Remove all distro source code sources"""
  355.     sources = []
  356.     sources.extend(self.distro.main_sources)
  357.     sources.extend(self.distro.child_sources)
  358.     # remove all exisiting sources
  359.     for source in self.distro.source_code_sources:
  360.         self.sourceslist.remove(source)
  361.     self.set_modified_sourceslist()
  362.   
  363.   def enable_source_code_sources(self):
  364.     """Enable source code source for all distro sources"""
  365.     sources = []
  366.     sources.extend(self.distro.main_sources)
  367.     sources.extend(self.distro.child_sources)
  368.  
  369.     # remove all exisiting sources
  370.     for source in self.distro.source_code_sources:
  371.         self.sourceslist.remove(source)
  372.  
  373.     for source in sources:
  374.         self.sourceslist.add("deb-src",
  375.                              source.uri,
  376.                              source.dist,
  377.                              source.comps,
  378.                              "Added by software-properties",
  379.                              self.sourceslist.list.index(source)+1,
  380.                              source.file)
  381.     for source in self.distro.cdrom_sources:
  382.         self.sourceslist.add("deb-src",
  383.                              self.distro.source_template.base_uri,
  384.                              self.distro.source_template.name,
  385.                              source.comps,
  386.                              "Added by software-properties",
  387.                              self.sourceslist.list.index(source)+1,
  388.                              source.file)
  389.     self.set_modified_sourceslist()
  390.  
  391.   def backup_sourceslist(self):
  392.     """Store a backup of the source.list in memory"""
  393.     self.sourceslist_backup = []
  394.     for source in self.sourceslist.list:
  395.         source_bkp = SourceEntry(line=source.line,file=source.file)
  396.         self.sourceslist_backup.append(source_bkp)
  397.   
  398.   def toggle_source_use(self, source):
  399.     """Enable or disable the selected channel"""
  400.     #FIXME cdroms need to disable the comps in the childs and sources
  401.     source.disabled = not source.disabled
  402.     self.set_modified_sourceslist()
  403.  
  404.   def revert(self):
  405.     """Revert all settings to the state when software-properties 
  406.        was launched"""
  407.     #FIXME: GPG keys are still missing
  408.     self.restore_apt_conf()
  409.     self.revert_sourceslist()
  410.  
  411.   def revert_sourceslist(self):
  412.     """Restore the source list from the startup of the dialog"""
  413.     self.sourceslist.list = []
  414.     for source in self.sourceslist_backup:
  415.         source_reset = SourceEntry(line=source.line,file=source.file)
  416.         self.sourceslist.list.append(source_reset)
  417.     self.save_sourceslist()
  418.     self.reload_sourceslist()
  419.  
  420.   def set_modified_sourceslist(self):
  421.     """The sources list was changed and now needs to be saved and reloaded"""
  422.     self.modified_sourceslist = True
  423.     if self.options and self.options.massive_debug:
  424.         self.massive_debug_output()
  425.     self.save_sourceslist()
  426.     self.reload_sourceslist()
  427.  
  428.   def set_modified_config(self):
  429.     """Write the changed apt configuration to file"""
  430.     self.write_config()
  431.  
  432.   def render_source(self, source):
  433.     """Render a nice output to show the source in a treeview"""
  434.     if source.template == None:
  435.         if source.comment:
  436.             contents = "<b>%s</b>" % escape(source.comment)
  437.             # Only show the components if there are more than one
  438.             if len(source.comps) > 1:
  439.                 for c in source.comps:
  440.                     contents += " %s" % c
  441.         else:
  442.             contents = "<b>%s %s</b>" % (source.uri, source.dist)
  443.             for c in source.comps:
  444.                 contents += " %s" % c
  445.         if source.type in ("deb-src", "rpm-src"):
  446.             contents += " %s" % _("(Source Code)")
  447.         return contents
  448.     else:
  449.         # try to make use of a corresponding template
  450.         contents = "<b>%s</b>" % source.template.description
  451.         if source.type in ("deb-src", "rpm-src"):
  452.             contents += " (%s)" % _("Source Code")
  453.         if source.comment:
  454.             contents +=" %s" % source.comment
  455.         if source.template.child == False:
  456.             for comp in source.comps:
  457.                 if source.template.has_component(comp):
  458.                     # fixme: move something like this into distinfo.Template
  459.                     #        (why not use a dictionary again?)
  460.                     for c in source.template.components:
  461.                         if c.name == comp:
  462.                             contents += "\n%s" % c.description
  463.                 else:
  464.                     contents += "\n%s" % comp
  465.         return contents
  466.  
  467.   def get_comparable(self, source):
  468.       """extract attributes to sort the sources"""
  469.       cur_sys = 1
  470.       has_template = 1
  471.       has_comment = 1
  472.       is_source = 1
  473.       revert_numbers = string.maketrans("0123456789", "9876543210")
  474.       if source.template:
  475.         has_template = 0
  476.         desc = source.template.description
  477.         if source.template.distribution == self.distro:
  478.             cur_sys = 0
  479.       else:
  480.           desc = "%s %s %s" % (source.uri, source.dist, source.comps)
  481.           if source.comment:
  482.               has_comment = 0
  483.       if source.type.find("src"):
  484.           is_source = 0
  485.       return (cur_sys, has_template, has_comment, is_source,
  486.               desc.translate(revert_numbers))
  487.  
  488.   def get_isv_sources(self):
  489.     """Return a list of sources that are not part of the distribution"""
  490.     isv_sources = []
  491.     for source in self.sourceslist.list:
  492.         if not source.invalid and\
  493.            (source not in self.distro.main_sources and\
  494.             source not in self.distro.cdrom_sources and\
  495.             source not in self.distro.child_sources and\
  496.             source not in self.distro.disabled_sources) and\
  497.            source not in self.distro.source_code_sources:
  498.             isv_sources.append(source)
  499.     return isv_sources
  500.  
  501.   def get_cdrom_sources(self):
  502.     """Return the list of CDROM based distro sources"""
  503.     return self.distro.cdrom_sources
  504.       
  505.   def get_comp_download_state(self, comp):
  506.     """Return a tuple: the first value describes if a component is enabled
  507.        in the Internet repositories. The second value describes if the
  508.        first value is inconsistent."""
  509.     #FIXME: also return a correct inconsistent value
  510.     return (comp.name in self.distro.download_comps, False)
  511.  
  512.   def get_comp_child_state(self, template):
  513.     """Return a tuple: the first value describes if a component is enabled
  514.        in one of the child source that matcth the given template. 
  515.        The second value describes if the first value is inconsistent."""
  516.     comps = []
  517.     for child in self.distro.child_sources:
  518.         if child.template == template:
  519.             comps.extend(child.comps)
  520.     if len(comps) > 0 and \
  521.         len(self.distro.enabled_comps ^ set(comps)) == 0:
  522.         # All enabled distro components are also enabled for the child source
  523.         return (True, False)
  524.     elif len(comps) > 0 and\
  525.         len(self.distro.enabled_comps ^ set(comps)) != 0:
  526.         # A matching child source does exist but doesn't include all 
  527.         # enabled distro components
  528.         return(False, True)
  529.     else:
  530.         # There is no corresponding child source at all
  531.         return (False, False)
  532.   
  533.   def reload_sourceslist(self):
  534.     self.sourceslist.refresh()
  535.     self.sourceslist_visible=[]
  536.     self.distro.get_sources(self.sourceslist)    
  537.  
  538.   def write_config(self):
  539.     """Write the current apt configuration to file"""
  540.     # update the adept file as well if it is there
  541.     conffiles = ["/etc/apt/apt.conf.d/10periodic",
  542.                  "/etc/apt/apt.conf.d/20auto-upgrades",
  543.                  "/etc/apt/apt.conf.d/15adept-periodic-update"]
  544.  
  545.     # check (beforehand) if one exists, if not create one
  546.     for f in conffiles:
  547.       if os.path.isfile(f):
  548.         break
  549.     else:
  550.       print "No config found, creating one"
  551.       open(conffiles[0], "w")
  552.  
  553.     # ensure /etc/cron.daily/apt is executable
  554.     ac = "/etc/cron.daily/apt"
  555.     if os.path.exists(ac):
  556.       perm = os.stat(ac)[stat.ST_MODE]
  557.       if not (perm & stat.S_IXUSR):
  558.         print "file '%s' not executable, fixing" % ac
  559.         os.chmod(ac, 0755)
  560.  
  561.     # now update them
  562.     for periodic in conffiles:
  563.       # read the old content first
  564.       content = []
  565.       if os.path.isfile(periodic):
  566.         content = open(periodic, "r").readlines()
  567.         cnf = apt_pkg.Config.SubTree("APT::Periodic")
  568.  
  569.         # then write a new file without the updated keys
  570.         f = open(periodic, "w")
  571.         for line in content:
  572.           for key in cnf.List():
  573.             if line.find("APT::Periodic::%s" % (key)) >= 0:
  574.               break
  575.           else:
  576.             f.write(line)
  577.  
  578.         # and append the updated keys
  579.         for i in cnf.List():
  580.           f.write("APT::Periodic::%s \"%s\";\n" % (i, cnf.FindI(i)))
  581.         f.close()    
  582.  
  583.   def save_sourceslist(self):
  584.     """Backup the existing sources.list files and write the current 
  585.        configuration"""
  586.     self.sourceslist.backup(".save")
  587.     self.sourceslist.save()
  588.  
  589.   def _is_line_in_whitelisted_channel(self, srcline):
  590.     """
  591.     helper that checks if a given line is in the source list
  592.     return the channel name or None if not found
  593.     """
  594.     srcentry = SourceEntry(srcline)    
  595.     if os.path.exists(self.CHANNEL_PATH):
  596.       for f in glob.glob("%s/*.list" % self.CHANNEL_PATH):
  597.         for line in open(f):
  598.           if line.strip().startswith("#"):
  599.             continue
  600.           if srcentry == SourceEntry(line):
  601.             return os.path.splitext(os.path.basename(f))[0]
  602.     return None
  603.  
  604.   def check_and_add_key_for_whitelisted_channels(self, srcline):
  605.     """
  606.     helper that adds the gpg key of the channel to the apt
  607.     keyring *if* the channel is in the whitelist
  608.     /usr/share/app-install/channels or it is a public Launchpad PPA.
  609.     """
  610.     channel = self._is_line_in_whitelisted_channel(srcline)
  611.     if channel:
  612.       keyp = "%s/%s.key" % (self.CHANNEL_PATH, channel)
  613.       self.add_key(keyp)
  614.     # FIXME: abstract this all alway into the ppa.py file
  615.     parsed_uri = urlparse(SourceEntry(srcline).uri)
  616.     if parsed_uri.netloc == 'ppa.launchpad.net':
  617.       worker = AddPPASigningKeyThread(parsed_uri.path)
  618.       worker.start()
  619.  
  620.   def update_interface(self):
  621.     " abstract interface to keep the UI alive "
  622.  
  623.   def add_source_from_line(self, line):
  624.     """
  625.     Add a source with the given apt line and auto-add
  626.     signing key if we have it in the whitelist
  627.     """
  628.     (line, file) = expand_ppa_line(line.strip(), self.distro.codename)
  629.     self.check_and_add_key_for_whitelisted_channels(line)
  630.     new_entry = SourceEntry(line, file)
  631.     if new_entry.invalid:
  632.       return False
  633.     self.sourceslist.list.append(new_entry)
  634.     self.set_modified_sourceslist()
  635.     return True
  636.  
  637.   def remove_source(self, source):
  638.     """Remove the given source"""
  639.     # if its a sources.list.d file and it contains only a single line
  640.     # (the line that we just remove), then all references to that
  641.     # file are gone and the file is not saved. we work around that
  642.     # here
  643.     if source.file != apt_pkg.Config.FindFile("Dir::Etc::sourcelist"):
  644.       self.sourceslist.list.append(SourceEntry("", file=source.file))
  645.     self.sourceslist.remove(source)
  646.     self.set_modified_sourceslist()
  647.  
  648.   def add_key(self, path):
  649.     """Add a gnupg key to the list of trusted software vendors"""
  650.     if not os.path.exists(path):
  651.         return False
  652.     try:
  653.         return self.apt_key.add(path)
  654.     except:
  655.         return False
  656.  
  657.   def add_key_from_data(self, keydata):
  658.     "Add a gnupg key from a data string (e.g. copy-n-paste)"
  659.     tmp = tempfile.NamedTemporaryFile()
  660.     tmp.write(keydata)
  661.     tmp.flush()
  662.     return self.add_key(tmp.name)
  663.  
  664.   def remove_key(self, fingerprint):
  665.     """Remove a gnupg key from the list of trusted software vendors"""
  666.     try:
  667.         self.apt_key.rm(fingerprint)
  668.         return True
  669.     except:
  670.         return False
  671.  
  672.  
  673.  
  674. if __name__ == "__main__":
  675.   sp = SoftwareProperties()
  676.   print sp.get_release_upgrades_policy()
  677.   sp.set_release_upgrades_policy(0)
  678.